//********************************************************************************************************
// Product Name: MapWindow.dll Alpha
// Description:  The core libraries for the MapWindow 6.0 project.
//
//********************************************************************************************************
// The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); 
// you may not use this file except in compliance with the License. You may obtain a copy of the License at 
// http://www.mozilla.org/MPL/ 
//
// Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF 
// ANY KIND, either expressed or implied. See the License for the specificlanguage governing rights and 
// limitations under the License. 
//
// The Original Code is MapWindow.dll for the MapWindow 6.0 project
//
// The Initial Developer of this Original Code is Ted Dunsford. Created in August, 2007.
// 
// Contributor(s): (Open source contributors should list themselves and their modifications here). 
//
//********************************************************************************************************
using System;
using System.Collections.Generic;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

using MapWindow.Geometries;
using MapWindow.Components;
using MapWindow.Data;
using MapWindow.Main;
using MapWindow.Forms;
using MapWindow.Drawing;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;


namespace MapWindow.DirectX
{
    /// <summary>
    /// This is should not be instantiated because it cannot in itself perform the necessary functions.
    /// Instead, most of the specified functionality must be implemented in the more specific classes.
    /// This is also why there is no direct constructor for this class.  You can use the static
    /// "FromFile" or "FromFeatureLayer" to create FeatureLayers from a file.
    /// </summary>
    public class FeatureLayerDX : FeatureLayer, IFeatureLayerDX
    {
        #region FeatureLayerDX

        #region Events


        /// <summary>
        /// Occurs after the selection has been cleared
        /// </summary>
        public event EventHandler<FeatureChangeArgs> SelectionCleared;

        /// <summary>
        /// Occurs after the selection has been extended
        /// </summary>
        public event EventHandler<FeatureChangeEnvelopeArgs> SelectionExtended;

        #endregion

        #region Variables


        private float _previousScale;

        private double _previousZ;

        private int _numParts;

        #endregion

        #region Constructors


        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="featureSet">The data bearing layer to apply new drawing characteristics to</param>
        protected FeatureLayerDX(IFeatureSet featureSet):base(featureSet)
        {
            Configure();
           
        }
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="featureSet">The data bearing layer to apply new drawing characteristics to</param>
        /// <param name="container">The container this layer should be added to</param>
        protected FeatureLayerDX(IFeatureSet featureSet, ICollection<ILayer> container)
            :base(featureSet, container)
        {

            Configure();

        }

        private void Configure()
        {
            _numParts = 1;
            
        }

        #endregion


        #region Methods



        /// <summary>
        /// This is overridden in order to add a check to see if we need to re-scale.  This allows us
        /// to do the re-scaling check one time, instead of repeating it during each part or stage.
        /// </summary>
        /// <param name="e">A DrawVerifyArgs</param>
        /// <returns></returns>
        protected virtual bool OnBeforeDrawing(DrawVerifyArgsDX e)
        {
            float scale = GetScale(e);

            if (this.Symbolizer.ScaleMode == ScaleModes.Symbolic)
            {

                if (scale < _previousScale * .8 || scale > _previousScale * 1.25)
                {
                    // Zoom has changed enough to invalidate this.  Since OnBeforeDrawing is checked before Initialization, 
                    // this should invalidate the drawing when we need it.
                    base.IsInitialized = false;
                    _previousScale = scale;
                }
            }
            if (BeforeDrawing == null) return false;
            BeforeDrawing(this, e);
            return e.Cancel;
        }
       

        #endregion

        #region Properties

       

        /// <summary>
        /// Gets or sets the number of parts for this layer.
        /// </summary>
        public int NumParts
        {
            get { return _numParts; }
            set { _numParts = value; }
        }

        /// <summary>
        /// This property is computed during re-scaling.  If the ScaleMode is set to Symbolic, this
        /// will represent the scale that was used when initializing the symbols.
        /// </summary>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual float PreviousScale
        {
            get
            {
                return _previousScale;
            }
            set
            {
                _previousScale = value; 
            }
        }
   
 


        #endregion

        #region EventHandlers

        
        private void ViewAttributes_Click(object sender, EventArgs e)
        {
            ShowAttributes();
        }

       


        #endregion

        /// <summary>
        /// Ensures that if the mode is symbolic, the initialization code will occur every time.
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnDrawSetup(DrawSetupArgs e)
        {
            
            if (Symbolizer.ScaleMode == ScaleModes.Symbolic)
            {
                double z = e.Camera.CameraLocation.Z;
                if (z > _previousZ * 2 || z < _previousZ / 2)
                {
                    base.IsInitialized = false;
                    _previousZ = z;
                }
               
            }
        }

        #region Static Methods

        /// <summary>
        /// The static methods serve as an impromptu factory, handling the typing as necessary
        /// </summary>
        /// <param name="featureSet">The data layer for a feature layer like a line, point or polygon layer.</param>
        /// <param name="container">The container to add the layer to</param>
        /// <param name="progressHandler">A progress handler to override the default progress handler</param>
        /// <returns></returns>
        public static IFeatureLayerDX FromFeatureSet(IFeatureSet featureSet, ICollection<ILayer> container, IProgressHandler progressHandler)
        {
            if (featureSet.FeatureType == FeatureTypes.Line)
            {
                return new LineLayerDX(featureSet,  container, progressHandler);
            }
            if (featureSet.FeatureType == FeatureTypes.Point)
            {
                return new PointLayerDX(featureSet,  container, progressHandler);
            }
            if (featureSet.FeatureType == FeatureTypes.Polygon)
            {
                return new PolygonLayerDX(featureSet, container, progressHandler);
            }
            return new FeatureLayerDX(featureSet);
        }

        /// <summary>
        /// Creates a new instance of the appropriate subtype of FeatureLayer, depending on the
        /// characteristics of the file.  This will return either a PointLayer, a LineLayer
        /// or a PolygonLayer.
        /// </summary>
        /// <param name="filename">The string filename specifying the shapefile or other data source file to open. </param>
        /// <param name="progressHandler">A progress handling class</param>
        /// <param name="container">Any valid ICollection of ILayer to add the new layer to</param>
        /// <returns>A valid FeatureLayer interface that can be upcast to the appropriate Point, Line or Polygon type.</returns>
        /// 
        public static IFeatureLayerDX OpenFile(string filename, ICollection<ILayer> container,  IProgressHandler progressHandler)
        {
            IFeatureSet fs = FeatureSet.OpenFile(filename, progressHandler);
            if (fs.FeatureType == FeatureTypes.Line)
            {
                return new LineLayerDX(fs, container, progressHandler);
            }
            if (fs.FeatureType == FeatureTypes.Point)
            {
                return new PointLayerDX(fs, container,  progressHandler);
            }
            if (fs.FeatureType == FeatureTypes.Polygon)
            {
                return new PolygonLayerDX(fs, container, progressHandler);
            }
            return new FeatureLayerDX(fs, container);
        }

        /// <summary>
        /// Creates a new instance of the appropriate subtype of FeatureLayer, depending on the
        /// characteristics of the file.  This will return either a PointLayer, a LineLayer
        /// or a PolygonLayer.
        /// </summary>
        /// <param name="filename">The string filename specifying the shapefile or other data source file to open. </param>
        /// <param name="container">Any valid ICollection of ILayer that this layer should be added to</param>
        public static new IFeatureLayerDX OpenFile(string filename, ICollection<ILayer> container)
        {
            return OpenFile(filename, container, MapWindow.Components.DataManager.DefaultDataManager.ProgressHandler);
        }

        #endregion

        /// <summary>
        /// Fires the Selection Cleared Event
        /// </summary>
        /// <param name="e"></param>
        protected void OnSelectionCleared(FeatureChangeArgs e)
        {
            if (SelectionCleared != null) SelectionCleared(this, e);
        }

        /// <summary>
        /// Fires the Selection Extended event
        /// </summary>
        /// <param name="e"></param>
        protected void OnSelectionExtended(FeatureChangeEnvelopeArgs e)
        {
            if (SelectionExtended != null) SelectionExtended(this, e);
        }



       
        
        #endregion

        #region RenderBase

        #region Events

        /// <summary>
        /// Occurs immediately before drawing, allowing it to be cancelled
        /// </summary>
        public event EventHandler<DrawVerifyArgsDX> BeforeDrawing;

        /// <summary>
        /// Occurs after drawing, indicating if an exception or cancellation occured
        /// </summary>
        public event EventHandler<DrawCompletedArgsDX> DrawingCompleted;


        /// <summary>
        /// Occurs when the data has been loaded into the model after the first drawing method
        /// </summary>
        public event EventHandler<DrawArgsDX> Inititialized;




        #endregion

        #region Private Variables

        /// <summary>
        ///  Boolean, determines if this is visible or not
        /// </summary>

        private int _numStages = 1;

        #endregion

        #region Methods




        /// <summary>
        /// Causes the layer or object to draw itself to the map.  This may initiate
        /// one-time loading of buffers, but should not do heavy calculations every
        /// cycle.
        /// </summary>
        /// <param name="args">A Drawing Args structure.  This is used so that this method
        /// can also be called asynchronously</param>
        public virtual void Draw3D(DrawArgsDX args)
        {
            // try
            // {
            if (IsVisible == false)
            {
                OnDrawingCompleted(new DrawCompletedArgsDX(args, false, null));
                return;
            }
            if (OnBeforeDrawing(new DrawVerifyArgsDX(args, false)) == true)
            {
                OnDrawingCompleted(new DrawCompletedArgsDX(args, true, null));
                return;
            }

            OnDrawSetup(new DrawSetupArgs(args.Camera));
            args.Camera.Position(args.Device);

            if (IsInitialized == false)
            {
                Initialize(args);
            }




            for (int stage = 0; stage < NumStages; stage++)
            {
                for (int part = 0; part < NumParts; part++)
                {
                    DrawArgsDX innerArgs = new DrawArgsDX(args.Device, args.Camera, part, stage);
                    OnDraw(innerArgs);
                }
            }
            OnDrawingCompleted(new DrawCompletedArgsDX(args, false, null));
            //}
            //catch (Exception ex)
            //{
            //    OnDrawingCompleted(this, new DrawCompletedArgsDX(args, false, ex));
            //}


        }


        /// <summary>
        /// This handles the initialization where structures are created
        /// </summary>
        public virtual void Initialize(DrawArgsDX args)
        {
            // setup occurs here
            OnInitialize(args);
        }


        /// <summary>
        /// Determines the geographic distance to use for something that is one pixel wide on the screen
        /// </summary>
        /// <param name="args">A DrawArgsDX with a Microsoft.DirectX.Device</param>
        /// <returns>A float multiplier for the scale</returns>
        public float GetScale(DrawArgsDX args)
        {
            Viewport vp = args.Device.Viewport;
            Transforms t = args.Device.Transform;
            // Find the point along the view path of the camera that intersects with the 0 elevation of the z plane
            //Vector3 pt = Plane.IntersectLine(args.Camera.XYPlane, args.Camera.CameraPosition, args.Camera.TargetPosition);
            Vector3 pt = Vector3.Subtract(args.Camera.TargetPosition, args.Camera.CameraPosition);
            float z = Math.Abs(args.Camera.CameraPosition.Z);
            pt.X = pt.X * z / pt.Z;
            pt.Y = pt.Y * z / pt.Z;
            pt = Vector3.Add(args.Camera.CameraPosition, pt);
            pt.Z = 0;

            // Project that into screen space in order to determine where the 0-elevation plane is between the near plane an the far plane of the scene
            Vector3 depth = Vector3.Project(pt, vp, t.Projection, t.View, t.World);

            // Back project two pixels from the screen into world coordinates that are separated by a distance of 1 pixel
            Vector3 center = new Vector3(vp.Width / 2, vp.Height / 2, depth.Z);
            Vector3 right = new Vector3(vp.Width / 2 + 1, vp.Height / 2, depth.Z);
            Vector3 wCenter = Vector3.Unproject(center, vp, args.Device.Transform.Projection, args.Device.Transform.View, args.Device.Transform.World);
            Vector3 wRight = Vector3.Unproject(right, vp, args.Device.Transform.Projection, args.Device.Transform.View, args.Device.Transform.World);

            // The distance between the two locations in world coordinates gives a scale value.
            Vector3 diff = Vector3.Subtract(wRight, wCenter);

            return diff.Length();
        }



        #endregion

        #region Properties








        /// <summary>
        /// Gets the number of stages that will be drawn.  Each stage cycles through every part.
        /// </summary>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual int NumStages
        {
            get { return _numStages; }
            protected set { _numStages = value; }
        }




        #endregion

        #region Protected Methods




        /// <summary>
        /// This is the internal drawing method that follows the standard checks.  Drawing is evoked
        /// through the Draw3D command, but internally implemented in the DrawPart method.  After
        /// the initial checks, the Draw3D method will cycle the part integer from 0 to NumParts -1.
        /// </summary>
        /// <param name="e">A MapWindow.DirectX.Drawing.DrawArgsDXclass with the device and camera.</param>
        protected virtual void OnDraw(DrawArgsDX e)
        {
            // Meshes might have several parts to be drawn, so core drawing is done here.
            // This is not public because we do the basic checks against visible and 
            // initialize first.  This is overridden in sub-classes.
        }
      



        /// <summary>
        /// Fires an event when we are done with the drawing code, whether the drawing was successful, cancelled, or threw an exception.
        /// </summary>
        /// <param name="e">A DrawCompletedArgsDX parameter containing information about the drawing</param>
        protected virtual void OnDrawingCompleted(DrawCompletedArgsDX e)
        {
            if (DrawingCompleted == null) return;
            DrawingCompleted(this, e);
        }



        /// <summary>
        /// Fires the Initialized event
        /// </summary>
        /// <param name="e">An EventArgs parameter</param>
        protected virtual void OnInitialize(DrawArgsDX e)
        {
            IsInitialized = true;
            if (Inititialized != null)
            {
                Inititialized(this, e);
            }

        }


        #endregion

        #endregion
       
    }


}
